package com.dlc.shop.common.util;

import cn.hutool.core.util.StrUtil;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.connection.ReturnType;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.scripting.ScriptSource;
import org.springframework.scripting.support.ResourceScriptSource;

import java.util.List;

/**
 * @author LGH
 */
public class RedisLuaUtil {

    /**
     * 扣减库存(必须足够扣除的时候才会扣除，如果不够扣除返回负数，如果足够扣除返回剩余库存数量，否则不进行扣减)
     */
    public static final String DECREMENT_SCRIPT =
            "local stock = tonumber(redis.call('get', KEYS[1]))"
                    + "local num = tonumber(ARGV[1])"
                    + "local canUse = (stock - num)"
                    + "if (canUse < 0) then"
                    + "    return -1 "
                    + "end "
                    + "redis.call('set', KEYS[1], canUse)"
                    + "return canUse";

    /**
     * 合并扣减库存，在通过合并扣减库存的时候，无论库存有多少都会扣减，直到扣减到0为止，返回扣减的数量
     */
    public static final String MERGE_SCRIPT =
            "local stock = tonumber(redis.call('get', KEYS[1]))"
                    + "if (stock == 0) then"
                    + "    return 0"
                    + "end"
                    + "local num = tonumber(ARGV[1])"
                    + "local canUse = stock - num"
                    + "if (canUse > 0) then"
                    + "    redis.call('set', KEYS[1], canUse)"
                    + "else"
                    // 直到扣减到0为止
                    + "    redis.call('set', KEYS[1], 0)"
                    + "end"
                    + "return canUse";
    /**
     * 用scan命令来获取所有的lock的订单key
     * -- 返回每次的游标用于下次循环
     * return resp
     */
    public static final String GET_CANCEL_ORDER_SCRIPT =
            "local cachePrefix = KEYS[1]"
                    + "local index = tonumber(ARGV[1])"
                    + "local resp = redis.call('SCAN',index,'MATCH',cachePrefix,'COUNT',10000)"
                    // 返回每次的游标和数据用于下次循环，数组resp[1]是游标，resp[2]是数据
                    + "return resp";

    /**
     * 将脚本初始化为byte数组，方便后续传输
     * 静态参数，仅在启动服务时初始化一次
     */
    public static byte[] initByte(String luaPath){
        DefaultRedisScript<Object> script = new DefaultRedisScript<>();
        //读取脚本文件，设置到script对象
        ClassPathResource resource = new ClassPathResource(luaPath);
        ScriptSource scriptSource = new ResourceScriptSource(resource);
        script.setScriptSource(scriptSource);
        String scriptAsString = script.getScriptAsString();
        // 匹配替换掉lua中所有的注释、换行、多个空格，可以减少1/2到2/3的字节
        // 删除所有注释
        String luaData = scriptAsString.replaceAll("--.*", "");
        // 匹配多个空白字符替换成一个空格
        luaData = luaData.replaceAll("(\r)+", " ");
//        System.out.println("文件名称：" + luaPath);
//        System.out.println("正则替换前的字节长度：" + scriptAsString.getBytes().length);
//        System.out.println("正则替换后的字节长度：" + luaData.getBytes().length);
        return luaData.getBytes();
    }

    /**
     * 根据脚本执行结果返回一个字符串
     */
    public static String execute(StringRedisTemplate stringRedisTemplate, byte[] script, List<String[]> dataList) {
        byte[][] bss = getRequestBytes(dataList);

        return stringRedisTemplate.execute((RedisCallback<String>) connection -> {
            //执行Lua
            byte[] result =  connection.eval(script,
                    ReturnType.fromJavaType(byte.class),
                    dataList.size(),
                    bss);
            return result == null ? null : new String(result);
        });
    }

    /**
     * 真正调用redis来执行Lua的参数转换成bytes
     */
    public static byte[][] getRequestBytes(List<String[]> dataList) {

        int byteSize = dataList.size();

        byte[][] bss = new byte[byteSize][];

        for (int i = 0; i < byteSize; i++) {
            String[] arrays = dataList.get(i);
            StringBuilder stringBuilder = new StringBuilder();
            for (int j = 0; j < arrays.length; j++) {
                // 第一个数据不需要拼接符号，后面的需要
                if (j != 0) {
                    stringBuilder.append(StrUtil.C_UNDERLINE);
                }
                stringBuilder.append(arrays[j]);
            }
            bss[i] = stringBuilder.toString().getBytes();
        }
        return bss;
    }

    /**
     * 根据脚本执行结果返回一个字符串
     */
    public static String executeList(StringRedisTemplate stringRedisTemplate, byte[] script, List<String> dataList) {
        int byteSize = dataList.size();
        byte[][] bss = new byte[byteSize][];
        for (int i = 0; i < byteSize; i++) {
            bss[i] = dataList.get(i).getBytes();
        }

        return stringRedisTemplate.execute((RedisCallback<String>) connection -> {
            //执行Lua
            byte[] result =  connection.eval(script,
                    ReturnType.fromJavaType(byte.class),
                    dataList.size(),
                    bss);
            return result == null ? null : new String(result);
        });
    }
}
